Dependency Injection
The book has now been published and the content of this chapter has likely changed substanstially.Please see page 678 of xUnit Test Patterns for the latest information.
How do we design the system under test (SUT) so that we can replace its dependencies at run time?
The client provides the depended-on object to the SUT.
Sketch Dependency Injection embedded from Dependency Injection.gif
Almost every piece of code depends on some other classes, objects, modules or procedures. To unit test a piece of code properly, we would like to isolate it from its dependencies. This is hard to do if those dependencies are hard-coded within the code in the form of literal class names.
Dependency Injection is a way to allow the normal coupling between a SUT and its dependencies to be broken during automated testing.
How It Works
We avoid hard-coding the names of classes on which we depend into our code by providing some other means for the client or system configurator to tell the SUT in question what objects to use for each dependency as it is executed. As part of the design of the SUT we arrange to pass the dependency in to the SUT through the "front door". That is, setting the dependency becomes part of the API of the SUT. We can include is as an argument with each method call, on the constructor or we can make it a settable attribute (property.)
When To Use It
We need to provide a means to substitute a depended-on component (DOC) whenever we want to make it easy to use a Test Double (page X) to test our code. Static binding, that is, specifying exact types or classes at compile time, severely limits our options for how the software is configured as it runs. Dynamic binding allows software to be much more flexible by deferring the decison of exactly what type or class to use until run time. Dependency Injection is a good choice for communicating what class to use when we are designing the software from scratch. It is a natural way to design the code when we are doing TDD because many of the tests we write for dependant objects wants to replace the DOC with a Test Double.
When we don't have complete control over the code we are testing, such as when we are retrofitting tests to existing code that works("If it ain't broke, don't change it (even to improve the testability)" is a common though somewhat misguide constraint in these circumstances.), we may have to use some other means to introduce the Test Doubles. If the SUT uses Dependency Lookup (page X) to find the DOC we can override the lookup mechanism to return the Test Double. We can also use a Test-Specific Subclass (page X) of the SUT to return a Test Double as long as access to the DOC is encapsulated behind a method call.
Implementation Notes
Introducing Dependency Injection involves solving two problems. First, we must make it possible to use a Test Double wherever the real depended-on component is used. This is primarily an issue in statically-typed languages because it is a matter of having the compiler allow us to pass a Test Double off as the real thing. Secondly, we must provide a way to tell the SUT to use the Test Double.
Type Compatability
Whichever way we choose to install the dependency into the SUT, we also have to ensure that the Test Double we want to replace it with is "type-compatible" with the code that uses it. This is most easily accomplished if both the real component and the Test Double implement the same interface (in statically typed languages) or have the same signature (in dynamically typed languages.) A quick way to introduce a Test Double into existing code is to do an Extract Interface[Fowler] refactoring on the real DOC and then have the Test Double implement the new interface.
Installing the Test Double
There are a number of different ways to tell the SUT to use the Test Double; they all involve replacing the use of a hard-coded name with a mechanism that determines the type of object to use at execution time. The three basic options are:
- We can pass the dependency directly to the SUT as we invoke it. This is called Parameter Injection.
- We can tell the SUT what DOC to use when we construct it. This is called Constructor Injection.
- We can tell the SUT about the DOC sometime between when we constructed it and when we exercise it. This is call Setter Injection.
Each of these variations of Dependency Injection can be hand-coded but another option is to use an "Inversion of Control" (IoC) framework to link the various components together at run-time. This can avoid superflous diversity in how Dependency Injection is implemented across the application and can make reconfiguring the application for different deployment models much easier.
Variation: Parameter Injection
Parameter Injection is a form of Dependency Injection in which the SUT does not keep or initialize a reference to the DOC; instead, it is passed in as an argument of the method being called on the SUT. All clients of the SUT whether they are tests or production code, supply the depended-on component. This makes the SUT more independent of the context since it makes no assumptions about the dependency other than its usage interface. The main drawback is that it forces the client to know about the dependency. This is more appropriate in some circumstances than others. Most of the other variants of Dependency Injection move this knowledge somewhere other than the client or at least make it optional.
Parameter Injection is advocated by the original paper on Mock Objects (page X) [ET]. It is especially effective when doing true test-driven development because that's when we have the most control over the design. It is possible to introduce Parameter Injection in an optional fashion by providing an alternative signature for the method in question with the extra parameter and have the more traditional style method create the instance of the dependency and then call the method that takes the dependency as a parameter.
Variation: Constructor Injection
Both Constructor Injection and Setter Injection involve storing a reference to the DOC as an attribute (field or instance variable) of the SUT. With Dependency Injection, the field is initialized from a constructor argument. The SUT may optionally provide a simpler constructor that calls this constructor with the value normally used in production. When a test wants to replace the real depended-on component with a Test Double, it passes in the Test Double to the constructor when it builds the SUT.
This approach to introducing a Dependency Injection works well when there are only one or two constructors and they have small argument lists. It is the only approach that works if the DOC is an active object that creates its own thread of execution during construction; that would make it Hard-to-Test Code (page X) and we should probably consider turning it into a Humble Executable (see Humble Object on page X). If we have a large number of dependencies as constructor arguments, we probably need to refactor the code to remove this code smell.
Variation: Setter Injection
As with Constructor Injection, the SUT holds a reference to the DOC as an attribute (field) that is initialized in the constructor. Where it differs is that the attribute is exposed to the client either as a public attribute or via a "setter" method. When a test wants to replace the real depended-on component with a Test Double, it assigns to the exposed attribute (or calls the setter with) an instance of the Test Double. This works well when constructing the real DOC has no unpleasant side-effects and assuming that nothing can happen automatically between the constructor call and when the test calls the setter for the property. It cannot be used if the SUT does any significant processing in the constructor that relies on the dependency. In that case we can use Constructor Injection. If constructing the real DOC has deleterious side effects, we can avoid constructing it in the constructor by modifying the SUT to use Lazy Initialization[SBPP] to instantiate the DOC the first time the SUT needs to use it.
Retrofitting Dependency Injection
When the SUT does not support any of these options "out of the box" we may be able to retrofit this capability via a Test-Specific Subclass. If the actual class to be used is normally retrieved from configuration data, this retrieval should be done by some component other than the SUT and passed to the SUT using Dependency Injection. This use of the Humble Object pattern for the client or configurator decouples the SUT from the environment and ensures that tests do not need to set up some external dependency (the configuration file) to be able to introduce the Test Double.
Another possiblity is to use aspect-oriented programming (AOP) to insert the Dependency Injection mechanism in the development environment. It could be used to inject the decision to use the Test Double or it could involve injecting the test-specific logic -- the Test Double -- right into the SUT. I don't think we have enough experience with using AOP to call this a pattern just yet.
Motivating Example
This is an example of a test which cannot be made to pass "as is":
public void testDisplayCurrentTime_AtMidnight() { // fixture setup TimeDisplay sut = new TimeDisplay(); // exercise sut String result = sut.getCurrentTimeAsHtmlFragment(); // verify direct output String expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>"; assertEquals( expectedTimeString, result); } Example NondeterministicTest embedded from java/com/clrstream/ex7/test/TimeDisplayTest.java
This test almost always fails because it depends on the current time being returned to the SUT by a depended-on component. The values being returned by that component, the DefaultTimeProvider, cannot be controlled by the test. Therefore, this test will only pass when the system time is exactly midnight.
public String getCurrentTimeAsHtmlFragment() { Calendar currentTime; try { currentTime = new DefaultTimeProvider().getTime(); } catch (Exception e) { return e.getMessage(); } // etc.} Example HardCodedDependency embedded from java/com/clrstream/ex7/problem/TimeDisplay.java
Because the SUT is hard-coded to use a particular class to retrieve the time, there is no way to replace the depended-on component with a Test Double. That makes this test nondeterministic and pretty much useless. We need to find a way to get control of the indirect inputs of the SUT.
Refactoring Notes
We can use a Replace Dependency with Test Double (page X) refactoring to get control over the time. Setter Injection can be introduced into existing code if we have control over the code and the method in question is not widely used or we have refactoring tools that support the Introduce Parameter[JBrains] refactoring. Failing that, we can use an Extract Method[Fowler] refactoring to create the new method signature that takes the Dependency Injection as an argument and leave the old method as an Adapter[GOF] that calls the new method.
Example: Parameter Injection
Here's the test rewritten to use Parameter Injection:
public void testDisplayCurrentTime_AtMidnight_PI() { // Fixture setup: // Test Double instantiation TimeProvider tpStub = new MidnightTimeProvider(); // Instantiate SUT: TimeDisplay sut = new TimeDisplay(); // exercise sut using Test Double: String result = sut.getCurrentTimeAsHtmlFragment(tpStub); // verify outcome String expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>"; assertEquals("Midnight", expectedTimeString, result); } Example DependencyAsMethodParameter embedded from java/com/clrstream/ex7/test/TimeDisplayTestSolution.java
In this case only the test will use the new signature; the existing code can use the old signature and the method adapter instantiates the real dependency object before passing it in.
public String getCurrentTimeAsHtmlFragment( TimeProvider timeProviderArg) { Calendar currentTime; try { currentTime = timeProviderArg.getTime(); } catch (Exception e) { return e.getMessage(); } // etc.} Example DependencyAsMethodArgumentSut embedded from java/com/clrstream/ex7/TimeDisplay.java
Example: Constructor Injection
Here's the same test rewritten to use Constructor Injection:
public void testDisplayCurrentTime_AtMidnight_CI() throws Exception { // Fixture setup: // Test Double instantiation TimeProvider tpStub = new MidnightTimeProvider(); // Instantiate SUT injecting Test Double: TimeDisplay sut = new TimeDisplay(tpStub); // Exercise SUT: String expectedTimeString = "<span class=\"tinyBoldText\">12:01 AM</span>"; // Verify outcome assertEquals("12:01 AM", expectedTimeString, sut.getCurrentTimeAsHtmlFragment()); } Example DependencyAsConstructorArgument embedded from java/com/clrstream/ex7/test/TimeDisplayTestSolution.java
To refactor to Constructor Injection we can do an Introduce Field[JetBrains] refactoring to hold the DOC in a field that is initialized in the existing constructor. We can then an Introduce Parameter refactoring to modify all callers of the existing constructor to pass the real DOC as a parameter of the constructor. If we cannot or do not want to modify all existing callers of the constructor we can define a new constructor that takes the DOC as a parameter and modify the existing constructor to instantiate the real DOC and pass it into our new constructor.
public class TimeDisplay { private TimeProvider timeProvider; public TimeDisplay() { timeProvider = new DefaultTimeProvider(); } public TimeDisplay(TimeProvider timeProvider) { this.timeProvider = timeProvider; } Example DependencyAsConstructorArgSut embedded from java/com/clrstream/ex7/TimeDisplay.java
Another approach is to do an Extract Method refactoring on the call to the constructor and then use Move Method[Fowler] refactoring to move it to an Object Factory (see Dependency Lookup). That would result in Dependency Lookup.
Example: Setter Injection
Here is the same test refactored to use Setter Injection:
public void testDisplayCurrentTime_AtMidnight_SI() throws Exception { // Fixture setup: // Test Double instantiation TimeProvider tpStub = new MidnightTimeProvider(); // Instantiate SUT: TimeDisplay sut = new TimeDisplay(); // Test Double installation sut.setTimeProvider(tpStub); // exercise sut String result = sut.getCurrentTimeAsHtmlFragment(); // verify outcome String expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>"; assertEquals("Midnight", expectedTimeString, result); } Example DependencyAsAttribute embedded from java/com/clrstream/ex7/test/TimeDisplayTestSolution.java
Note the call to setTimeProvider to install the Hard-Coded Test Double (page X). If we had used a Configurable Test Double (page X) the configuration of it would occur immediately before the call to setTimeProvider.
To refactor to Setter Injection we can do an Introduce Field refactoring to hold the DOC in a variable that is initialized in the existing constructor and call the DOC via this field. We can then expose the field either directly or via a setter so that the test can override its value. Here is the refactored version of the SUT:
public class TimeDisplay { private TimeProvider timeProvider; public TimeDisplay() { timeProvider = new DefaultTimeProvider(); } public void setTimeProvider(TimeProvider provider) { this.timeProvider = provider; } public String getCurrentTimeAsHtmlFragment() throws TimeProviderEx { Calendar currentTime; try { currentTime = getTimeProvider().getTime(); } catch (Exception e) { return e.getMessage(); } // etc. Example DependencyAsAttributeSut embedded from java/com/clrstream/ex7/TimeDisplay.java
We chose to use a "getter" to retreive the DOC but could just as easily have used the timeProvider field directly.
Copyright © 2003-2008 Gerard Meszaros all rights reserved